// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {
    DescEnum,
    DescEnumValue,
    DescExtension,
    DescField,
    DescMessage, DescMethod,
    DescOneof,
    DescService, LongType, ScalarType, ScalarValue
} from "@bufbuild/protobuf";
import {reifyWkt} from "@bufbuild/protoplugin/ecmascript";
import {localName, safeIdentifier, safeObjectProperty} from "./names";
import {getUnwrappedFieldType, scalarZeroValue} from "./scalars";

export interface RuntimeImports {
    proto2: ImportSymbol;
    proto3: ImportSymbol;
    Message: ImportSymbol;
    FieldList: ImportSymbol;
    MessageType: ImportSymbol;
    BinaryReadOptions: ImportSymbol;
    BinaryWriteOptions: ImportSymbol;
    JsonReadOptions: ImportSymbol;
    JsonWriteOptions: ImportSymbol;
    JsonValue: ImportSymbol;
    protoInt64: ImportSymbol;
    ScalarType: ImportSymbol;
    EnumObject: ImportSymbol;
    FieldWrapper: ImportSymbol;
    makeEnumTypeInstance: ImportSymbol;
    EnumTypeInstance: ImportSymbol;
    EnumValueInfoInstance: ImportSymbol;
    getFieldListsFromArray: ImportSymbol;
    InternalOneofInfo: ImportSymbol;
    ProtoRuntime: ImportSymbol;
    Transport: ImportSymbol;
    CallOptions: ImportSymbol;
    WireType: ImportSymbol;
    EnumType: ImportSymbol;
    EnumValueInfo: ImportSymbol;
    FieldInfo: ImportSymbol;
    FieldValueInfo: ImportSymbol;
    fiMapInterface: ImportSymbol;
    OneofClass: ImportSymbol;
    EmptyMessage: ImportSymbol;
}

export function createRuntimeImports(): RuntimeImports {
    // prettier-ignore
    return {
        proto2: infoToSymbol("proto2"),
        proto3: infoToSymbol("proto3"),
        Message: infoToSymbol("Message"),
        FieldList: infoToSymbol("FieldList"),
        MessageType: infoToSymbol("MessageType"),
        BinaryReadOptions: infoToSymbol("BinaryReadOptions"),
        BinaryWriteOptions: infoToSymbol("BinaryWriteOptions"),
        JsonReadOptions: infoToSymbol("JsonReadOptions"),
        JsonWriteOptions: infoToSymbol("JsonWriteOptions"),
        JsonValue: infoToSymbol("JsonValue"),
        protoInt64: infoToSymbol("protoInt64"),
        ScalarType: infoToSymbol("ScalarType"),
        EnumObject: infoToSymbol("EnumObject"),
        FieldWrapper: infoToSymbol("FieldWrapper"),
        makeEnumTypeInstance: infoToSymbol("makeEnumTypeInstance"),
        EnumTypeInstance: infoToSymbol("EnumTypeInstance"),
        EnumValueInfoInstance: infoToSymbol("EnumValueInfoInstance"),
        getFieldListsFromArray: infoToSymbol("getFieldListsFromArray"),
        InternalOneofInfo: infoToSymbol("InternalOneofInfo"),
        ProtoRuntime: infoToSymbol("ProtoRuntime"),
        Transport: infoToSymbol("Transport"),
        CallOptions: infoToSymbol("CallOptions"),
        WireType: infoToSymbol("WireType"),
        EnumType: infoToSymbol("EnumType"),
        EnumValueInfo: infoToSymbol("EnumValueInfo"),
        FieldInfo: infoToSymbol("FieldInfo"),
        FieldValueInfo: infoToSymbol("FieldValueInfo"),
        fiMapInterface: infoToSymbol("fiMapInterface"),
        OneofClass: infoToSymbol("OneofClass"),
        EmptyMessage: infoToSymbol("EmptyMessage"),
    };
}

function infoToSymbol(
    name: keyof typeof codegenInfo.symbols,
): ImportSymbol {
    const info = codegenInfo.symbols[name];
    const symbol = createImportSymbol(
        name,
        info.publicImportPath,
    );
    return info.typeOnly ? symbol.toTypeOnly() : symbol;
}

export type ImportSymbol = {
    readonly kind: "es_symbol";

    /**
     * The name to import.
     */
    readonly name: string;

    /**
     * The import path.
     *
     * The path can point to a package, for example `@foo/bar/baz.js`, or
     * to a file, for example `./bar/baz.js`.
     *
     * Note that while paths to a file begin with a `./`, they must be
     * relative to the project root.
     */
    readonly from: string;

    /**
     * Whether this is a type-only import - an import that only exists in
     * TypeScript.
     */
    readonly typeOnly: boolean;

    /**
     * Create a copy of this import, and make it type-only for TypeScript.
     */
    toTypeOnly(): ImportSymbol;

    /**
     * The unique ID based on name and from, disregarding typeOnly.
     */
    readonly id: EsSymbolId;
};


function createImportSymbol(
    name: string,
    from: string,
    typeOnly?: boolean,
): ImportSymbol {
    const id = `import("${from}").${name}`;
    const s: ImportSymbol = {
        kind: "es_symbol",
        name,
        from,
        typeOnly: false,
        id,
        toTypeOnly() {
            return {
                ...this,
                typeOnly: true,
            };
        },
    };
    return typeOnly === true ? s.toTypeOnly() : s;
}

type EsSymbolId = string;

interface CodegenInfo {
    /**
     * Name of the runtime library NPM package.
     */
    readonly packageName: string;
    readonly localName: (
        desc:
            | DescEnum
            | DescEnumValue
            | DescMessage
            | DescExtension
            | DescOneof
            | DescField
            | DescService
            | DescMethod,
    ) => string;
    readonly symbols: Record<RuntimeSymbolName, RuntimeSymbolInfo>;
    readonly getUnwrappedFieldType: (
        field: DescField | DescExtension,
    ) => ScalarType | undefined;
    readonly wktSourceFiles: readonly string[];
    /**
     * @deprecated please use scalarZeroValue instead
     */
    readonly scalarDefaultValue: (type: ScalarType, longType: LongType) => any; // eslint-disable-line @typescript-eslint/no-explicit-any
    readonly scalarZeroValue: <T extends ScalarType, L extends LongType>(
        type: T,
        longType: L,
    ) => ScalarValue<T, L>;
    /**
     * @deprecated please use reifyWkt from @bufbuild/protoplugin/ecmascript instead
     */
    readonly reifyWkt: typeof reifyWkt;
    readonly safeIdentifier: (name: string) => string;
    readonly safeObjectProperty: (name: string) => string;
}

type RuntimeSymbolName =
    | "BinaryReadOptions"
    | "BinaryWriteOptions"
    | "JsonReadOptions"
    | "JsonWriteOptions"
    | "JsonValue"
    | "Message"
    | "MessageType"
    | "proto2"
    | "proto3"
    | "protoInt64"
    | "ScalarType"
    | "EnumObject"
    | "FieldWrapper"
    | "makeEnumTypeInstance"
    | "EnumTypeInstance"
    | "EnumValueInfoInstance"
    | "getFieldListsFromArray"
    | "InternalOneofInfo"
    | "ProtoRuntime"
    | "Transport"
    | "CallOptions"
    | 'WireType'
    | "EnumType"
    | "EnumValueInfo"
    | "FieldInfo"
    | "FieldValueInfo"
    | "fiMapInterface"
    | "OneofClass"
    | "FieldList"
    | 'EmptyMessage'


type RuntimeSymbolInfo = {
    typeOnly: boolean;
    publicImportPath: string;
};

const packageName = "@bitfun/protobuf_ets";

const codegenInfo: CodegenInfo = {
    packageName: "@bitfun/protobuf_ets",
    localName,
    reifyWkt,
    getUnwrappedFieldType,
    scalarDefaultValue: scalarZeroValue,
    scalarZeroValue,
    safeIdentifier,
    safeObjectProperty,
    // prettier-ignore
    symbols: {
        proto2: {typeOnly: false, publicImportPath: packageName},
        proto3: {typeOnly: false, publicImportPath: packageName},
        Message: {typeOnly: false, publicImportPath: packageName},
        FieldList: {typeOnly: false, publicImportPath: packageName},
        MessageType: {typeOnly: false, publicImportPath: packageName},
        BinaryReadOptions: {typeOnly: false, publicImportPath: packageName},
        BinaryWriteOptions: {typeOnly: false, publicImportPath: packageName},
        JsonReadOptions: {typeOnly: false, publicImportPath: packageName},
        JsonWriteOptions: {typeOnly: false, publicImportPath: packageName},
        JsonValue: {typeOnly: false, publicImportPath: packageName},
        protoInt64: {typeOnly: false, publicImportPath: packageName},
        ScalarType: {typeOnly: false, publicImportPath: packageName},
        WireType: {typeOnly: false, publicImportPath: packageName},
        EnumType: {typeOnly: false, publicImportPath: packageName},
        EnumValueInfo: {typeOnly: false, publicImportPath: packageName},
        FieldInfo: {typeOnly: false, publicImportPath: packageName},
        FieldValueInfo: {typeOnly: false, publicImportPath: packageName},
        fiMapInterface: {typeOnly: false, publicImportPath: packageName},
        OneofClass: {typeOnly: false, publicImportPath: packageName},
        EmptyMessage: {typeOnly: false, publicImportPath: packageName},
        EnumObject: {typeOnly: false, publicImportPath: packageName},
        FieldWrapper: {typeOnly: false, publicImportPath: packageName},
        makeEnumTypeInstance: {typeOnly: false, publicImportPath: packageName},
        EnumTypeInstance: {typeOnly: false, publicImportPath: packageName},
        EnumValueInfoInstance: {typeOnly: false, publicImportPath: packageName},
        getFieldListsFromArray: {typeOnly: false, publicImportPath: packageName},
        InternalOneofInfo: {typeOnly: false, publicImportPath: packageName},
        ProtoRuntime: {typeOnly: false, publicImportPath: packageName},
        Transport: {typeOnly: false, publicImportPath: packageName},
        CallOptions: {typeOnly: false, publicImportPath: packageName},
    },
    wktSourceFiles: [
        "google/protobuf/compiler/plugin.proto",
        "google/protobuf/any.proto",
        "google/protobuf/api.proto",
        "google/protobuf/descriptor.proto",
        "google/protobuf/duration.proto",
        "google/protobuf/empty.proto",
        "google/protobuf/field_mask.proto",
        "google/protobuf/source_context.proto",
        "google/protobuf/struct.proto",
        "google/protobuf/timestamp.proto",
        "google/protobuf/type.proto",
        "google/protobuf/wrappers.proto",
    ],
};
